package com.lechuang.file.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.builder.ExcelWriterTableBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.lechuang.file.dto.ExcelRemarkDTO;
import com.lechuang.file.dto.ExportClassDTO;
import com.lechuang.file.dto.ExportStringDTO;
import com.lechuang.file.strategy.DataCellMergeStrategy;
import com.lechuang.file.strategy.RemarkCellMergeStrategy;
import com.lechuang.file.style.DataCellStyle;
import com.lechuang.file.style.RemarkCellStyle;
import com.lechuang.file.style.TitleCellStyle;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections4.CollectionUtils;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * <p>
 *
 * </p>
 * @author liaoningbo
 * @since 2023/6/7
*/
public class WriteExcelUtils {

    public static void setHeader(HttpServletResponse httpServletResponse, String fileName){
        try{
            httpServletResponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            httpServletResponse.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            fileName = URLEncoder.encode(fileName, "UTF-8");
            httpServletResponse.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            httpServletResponse.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * <p>
     *   导出excel
     * </p>
     * @author liaoningbo
     * @since 2023/5/22
    */
    public static <T> void exportExcel(ExportClassDTO<T> exportClassDTO) {
        try{
            HttpServletResponse httpServletResponse = exportClassDTO.getHttpServletResponse();
            String fileName = StrUtil.isEmpty(exportClassDTO.getFileName()) ? "" : exportClassDTO.getFileName();
            setHeader(httpServletResponse, fileName);
            write(exportClassDTO, httpServletResponse.getOutputStream());
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * 将参数写入excel中
     * @param data
     * @param clazz
     * @param fileName
     * @param sheetName
     * @param <T>
     * @return
     */
    public static <T>ByteArrayOutputStream builderDataToExcel(List<T> data,Class<T> clazz, String fileName, String sheetName) {

        if(CollectionUtils.isEmpty(data) || Objects.isNull(clazz) || StrUtil.isBlank(fileName) || StrUtil.isBlank(sheetName)){
            throw new RuntimeException("数据转excel参数信息有误");
        }
        ExportClassDTO<T> exportClassDTO = new ExportClassDTO<T>()
                .setFileName(fileName).setSheetName(sheetName).setDataList(data)
                .setDataClass(clazz);
        return WriteExcelUtils.getExcelOutputStream(exportClassDTO);
    }

    /**
     * <p>
     *   将excel写入流中
     * </p>
     * @author liaoningbo
     * @since 2023/5/22
    */
    public static <T> ByteArrayOutputStream getExcelOutputStream(ExportClassDTO<T> exportClassDTO) {
        try{
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            write(exportClassDTO, outputStream);
            return outputStream;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * <p>
     *   将excel写入请求参数的流中
     * </p>
     * @author liaoningbo
     * @since 2023/5/22
    */
    private static <T> void write(ExportClassDTO<T> exportClassDTO, OutputStream outputStream) {
        String fileName = StrUtil.isEmpty(exportClassDTO.getFileName()) ? "" : exportClassDTO.getFileName();
        String sheetName = StrUtil.isEmpty(exportClassDTO.getSheetName()) ? fileName : exportClassDTO.getSheetName();
        List<T> list = CollUtil.isEmpty(exportClassDTO.getDataList()) ? new ArrayList<>() : exportClassDTO.getDataList() ;
        Class<T> dataClass = exportClassDTO.getDataClass();
        String title = exportClassDTO.getTitle();
        List<String> remarks = exportClassDTO.getRemarkList();
        Integer mergeRowIndex = exportClassDTO.getMergeRowIndex();
        int[] mergeColumnIndex = exportClassDTO.getMergeColumnIndex();

        int rowCount = getColumnSize(dataClass);

        ExcelWriter excelWriter = EasyExcel.write(outputStream).build();
        // 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了
        WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).needHead(Boolean.FALSE).build();

        int i = 0;
        //写入标题
        if(StrUtil.isNotEmpty(title)){
            List<ExcelRemarkDTO> titleList = new ArrayList<>();
            titleList.add(new ExcelRemarkDTO(title));
            WriteTable remarkTable = EasyExcel.writerTable(i)
                    .head(ExcelRemarkDTO.class)
                    .needHead(Boolean.FALSE)
                    .registerWriteHandler(new TitleCellStyle())
                    .registerWriteHandler(new RemarkCellMergeStrategy(rowCount))
                    .build();
            excelWriter.write(titleList, writeSheet, remarkTable);
            i ++;
        }

        //写入备注行
        if(CollUtil.isNotEmpty(remarks)){
            List<ExcelRemarkDTO> remarkList = new ArrayList<>();
            remarks.forEach(bean -> {
                remarkList.add(new ExcelRemarkDTO(bean));
            });
            WriteTable remarkTable = EasyExcel.writerTable(i)
                    .head(ExcelRemarkDTO.class)
                    .needHead(Boolean.FALSE)
                    .registerWriteHandler(new RemarkCellMergeStrategy(rowCount))
                    .registerWriteHandler(new RemarkCellStyle())
                    .build();
            excelWriter.write(remarkList, writeSheet, remarkTable);
            i ++;
        }
        ExcelWriterTableBuilder excelWriterTableBuilder;
        //写入数据行
        excelWriterTableBuilder = EasyExcel.writerTable(i)
                .head(dataClass)
                .needHead(Boolean.TRUE)
                .registerWriteHandler(new DataCellStyle())
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());
        if(mergeRowIndex != null && mergeColumnIndex != null){
            excelWriterTableBuilder.registerWriteHandler(new DataCellMergeStrategy(mergeRowIndex, mergeColumnIndex));
        }
        excelWriter.write(list, writeSheet, excelWriterTableBuilder.build());

        excelWriter.finish();
    }

    public static void exportExcel(ExportStringDTO exportStringDTO) {
        try{
            HttpServletResponse httpServletResponse = exportStringDTO.getHttpServletResponse();
            String fileName = StrUtil.isEmpty(exportStringDTO.getFileName()) ? "" : exportStringDTO.getFileName();
            setHeader(httpServletResponse, fileName);
            write(exportStringDTO, httpServletResponse.getOutputStream());
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * <p>
     *   将excel写入流中
     * </p>
     * @author liaoningbo
     * @since 2023/5/22
     */
    public static ByteArrayOutputStream getExcelOutputStream(ExportStringDTO exportStringDTO) {
        try{
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            write(exportStringDTO, outputStream);
            return outputStream;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * <p>
     *   将excel写入请求参数的流中
     * </p>
     * @author liaoningbo
     * @since 2023/5/22
     */
    private static void write(ExportStringDTO exportStringDTO, OutputStream outputStream) {
        String fileName = StrUtil.isEmpty(exportStringDTO.getFileName()) ? "" : exportStringDTO.getFileName();
        String sheetName = StrUtil.isEmpty(exportStringDTO.getSheetName()) ? fileName : exportStringDTO.getFileName();
        List<List<Object>> list = exportStringDTO.getDataList();
        List<List<String>> heads = exportStringDTO.getHeads();
        String title = exportStringDTO.getTitle();
        List<String> remarks = exportStringDTO.getRemarkList();
        Integer mergeRowIndex = exportStringDTO.getMergeRowIndex();
        int[] mergeColumnIndex = exportStringDTO.getMergeColumnIndex();

        int rowCount = heads.size();

        ExcelWriter excelWriter = EasyExcel.write(outputStream).build();
        // 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了
        WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).needHead(Boolean.FALSE).build();

        int i = 0;
        //写入标题
        if(StrUtil.isNotEmpty(title)){
            List<ExcelRemarkDTO> titleList = new ArrayList<>();
            titleList.add(new ExcelRemarkDTO(title));
            WriteTable remarkTable = EasyExcel.writerTable(i)
                    .head(ExcelRemarkDTO.class)
                    .needHead(Boolean.FALSE)
                    .registerWriteHandler(new TitleCellStyle())
                    .registerWriteHandler(new RemarkCellMergeStrategy(rowCount))
                    .build();
            excelWriter.write(titleList, writeSheet, remarkTable);
            i ++;
        }

        //写入备注行
        if(CollUtil.isNotEmpty(remarks)){
            List<ExcelRemarkDTO> remarkList = new ArrayList<>();
            remarks.forEach(bean -> {
                remarkList.add(new ExcelRemarkDTO(bean));
            });
            WriteTable remarkTable = EasyExcel.writerTable(i)
                    .head(ExcelRemarkDTO.class)
                    .needHead(Boolean.FALSE)
                    .registerWriteHandler(new RemarkCellMergeStrategy(rowCount))
                    .registerWriteHandler(new RemarkCellStyle())
                    .build();
            excelWriter.write(remarkList, writeSheet, remarkTable);
            i ++;
        }
        ExcelWriterTableBuilder excelWriterTableBuilder;
        //写入数据行
        excelWriterTableBuilder = EasyExcel.writerTable(i)
                .head(heads)
                .needHead(Boolean.TRUE)
                .registerWriteHandler(new DataCellStyle())
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());
        if(mergeRowIndex != null && mergeColumnIndex != null){
            excelWriterTableBuilder.registerWriteHandler(new DataCellMergeStrategy(mergeRowIndex, mergeColumnIndex));
        }
        excelWriter.write(list, writeSheet, excelWriterTableBuilder.build());

        excelWriter.finish();
    }

    /**
     * <p>
     *   获取数据行占用的列数，用于标题和备注行合并单元格用
     * </p>
     * @author liaoningbo
     * @since 2023/5/22
    */
    private static <T> Integer getColumnSize(Class<T> tClass) {
        Field[] fields = tClass.getDeclaredFields();
        List<Field> fieldList = new ArrayList<>();
        for (Field field : fields) {
            field.setAccessible(true);
            //判断是否使用ExcelAnnotation注解
            if(field.isAnnotationPresent(ExcelProperty.class)){
                fieldList.add(field);
            }
        }
        return fieldList.size();
    }
}
